﻿using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;

namespace System.Windows.Reactive
{
	/// <summary>
	/// Provides high-level access to the definition of a binding that supports <see cref="IObservable{T}"/> and <see cref="IObserver{T}"/>.
	/// A binding connects the properties of binding target objects (typically, WPF elements), and any observable or scalar data source.
	/// <see cref="Subscription"/> behaves almost identically to <see cref="Binding"/>, expect that <see cref="IObservable{T}"/> data sources
	/// update the target with each element in the sequence and <see cref="IObserver{T}"/> data sources are notified when the target changes.
	/// </summary>
	/// <remarks>
	/// <para>
	/// Binding to objects that do not implement <see cref="IObservable{T}"/> is supported in the direction from the source to the target, 
	/// just like <see cref="Binding"/>.  Updates from the target to the source are supported only if the source implements 
	/// <see cref="IObserver{T}"/>.
	/// </para>
	/// <para>
	/// A source that does not implement either <see cref="IObserver{T}"/> or <see cref="IObservable{T}"/> behaves like a normal 
	/// <see cref="Binding"/> with its <see cref="Binding.Mode"/> property set to <see cref="BindingMode.OneWay"/>.
	/// </para>
	/// <para>
	/// The target may require a scalar value.  For example, the <see cref="TextBlock.Text"/> property is usually bound to a property that 
	/// returns a <see cref="string"/>.  When the data source is an <see cref="IObservable{T}"/> sequence, instead of a scalar value, it is 
	/// treated like a changing scalar value.  Each element in the <see cref="IObservable{T}"/> is pushed to the target as the latest value.
	/// Essentially, subscribing to a property that returns <see cref="IObservable{T}"/> is a reactive alternative to binding to a property 
	/// that returns a scalar value, implementing <see cref="INotifyPropertyChanged"/> and raising an event each time the scalar value changes.
	/// </para>
	/// <para>
	/// <see cref="Subscription"/> also supports <see cref="System.Collections.IEnumerable"/> targets.  For example, <see cref="Subscription"/> 
	/// can be used to bind an observable data source to the <see cref="ItemsControl.ItemsSource"/> property.  The behavior of this kind of 
	/// subscription depends upon the capabilities of the source.  If the source implements <see cref="System.Collections.IEnumerable"/>, then 
	/// the source is sent to the target unmodified.  If the source also implements <see cref="System.Collections.Specialized.INotifyCollectionChanged"/>, 
	/// then the target is updated as per the specifications of that interface.  For example, this behavior is applied when subscribing to 
	/// <see cref="System.Reactive.Subjects.IListSubject{T}"/> or <see cref="System.Reactive.Subjects.IDictionarySubject{TKey,TValue}"/> 
	/// data sources.
	/// </para>
	/// <para>
	/// If the source does not implement <see cref="System.Collections.Specialized.INotifyCollectionChanged"/>, although it does implement 
	/// <see cref="IObservable{T}"/>, then the target is reset for every element in the observable sequence.  This causes the target to 
	/// iterate the entire enumerable sequence each time that an element is observed in the observable sequence.  In other words, the observable 
	/// is used as a change notification for the enumerable, although the actual data in the observable sequence is ignored.
	/// </para>
	/// <para>
	/// If the source does not implement <see cref="System.Collections.IEnumerable"/>, but does implement <see cref="IObservable{T}"/>, 
	/// then the target is bound to an empty collection of objects.  Then, each <see cref="System.Reactive.CollectionNotification{T}"/> 
	/// that is observed from the source observable sequence is applied to the bound collection.  Any values that aren't a 
	/// <see cref="System.Reactive.CollectionNotification{T}"/> are simply added to the bound collection.
	/// </para>
	/// <alert type="tip">
	/// All observed notifications are automatically marshaled to the dispatcher thread, unlike <see cref="Binding"/>.  For <see cref="IObservable{T}"/>
	/// sources, this means that you don't have to add <strong>ObserveOnDispatcher</strong> to your query.  For 
	/// <see cref="System.Collections.Specialized.INotifyCollectionChanged"/> sources, you can make concurrent updates to the source collection 
	/// (provided that it's thread-safe already) and the <see cref="Subscription"/> will automatically marshal changes to the dispatcher thread.
	/// </alert>
	/// <para>
	/// Otherwise, if the source does not implement either <see cref="System.Collections.IEnumerable"/> or <see cref="IObservable{T}"/>, 
	/// then the behavior used is the same as <see cref="Binding"/>.  In this case, you'll probably want to assign a <see cref="Converter"/> 
	/// that can convert the source into an <see cref="System.Collections.IEnumerable"/> for the target.
	/// </para>
	/// <alert type="warning">
	/// WPF may process bindings even before a UI has been completely loaded.  This can cause <see cref="Subscription"/> to receive a reference 
	/// to an observable data source before the UI is displayed, or even if it's never displayed.  Since <see cref="Subscription"/> does not know
	/// about the context in which its target element is being used, it will subscribe to the observable query right away.  In cases when you 
	/// need to instantiate an element that may contain <see cref="Subscription"/> bindings in XAML, but it's not added to a visual tree right 
	/// away or it's never going to be added, then it's recommended that you use <see cref="System.Reactive.Subjects.Subject{T}"/> as your 
	/// data source so that you can control when the source is connected to an observable that may cause side-effects.  When the element 
	/// finally raises its <see cref="FrameworkElement.Loaded"/> event, create the real observable sequence and subscribe the subject to it
	/// so that the binding starts receiving values.
	/// </alert>
	/// <para>
	/// The lifetime of the subscription to an observable data source is only partially controlled by the <see cref="Subscription"/> object.
	/// When the source property returns a different object, then the <see cref="Subscription"/> object will automatically dispose of 
	/// the previous observable's subscription, if any.  However, the latest subscription is never disposed by <see cref="Subscription"/>.
	/// For this reason, it remains the responsibility of data source objects to ensure that bound observables are cleaned up when they are 
	/// no longer needed.  This can be accomplished in a few different ways as follows.
	/// </para>
	/// <para>
	/// The most direct way is to call <see cref="Dispose"/> on the <see cref="Subscription"/> object, which dipsoses of any active subscription.
	/// </para>
	/// <para>
	/// In other cases you can ensure that the observable calls <strong>OnCompleted</strong>.  Or you can explicitly dispose of the 
	/// observable source, for example if you're using <see cref="System.Reactive.Subjects.Subject{T}"/> then you can call its 
	/// <see cref="IDisposable.Dispose"/> method when the bound element or its root container raises the <see cref="FrameworkElement.Unloaded"/> 
	/// event.
	/// </para>
	/// </remarks>
	public sealed class Subscription : MarkupExtension, IDisposable
	{
		#region Public Properties
		/// <summary>
		/// Gets or sets opaque data passed to the asynchronous data dispatcher.
		/// </summary>
		/// <value>Data passed to the asynchronous data dispatcher.</value>
		[DefaultValue("")]
		public object AsyncState
		{
			get
			{
				return binding.AsyncState;
			}
			set
			{
				binding.AsyncState = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether to evaluate the <see cref="Path"/> relative to the data item or 
		/// the <see cref="DataSourceProvider"/> object.
		/// </summary>
		/// <value><see langword="False"/> to evaluate the path relative to the data item itself; otherwise, <see langword="true"/>.
		/// The default is <see langword="false"/>.</value>
		[DefaultValue(false)]
		public bool BindsDirectlyToSource
		{
			get
			{
				return binding.BindsDirectlyToSource;
			}
			set
			{
				binding.BindsDirectlyToSource = value;
			}
		}

		/// <summary>
		/// Gets or sets the name of the element to use as the binding source object.
		/// </summary>
		/// <value>The value of the <strong>Name</strong> property or <strong>x:Name</strong> Attribute of the element of interest.
		/// You can refer to elements in code only if they are registered to the appropriate <see cref="NameScope"/> through <strong>RegisterName</strong>.
		/// The default is <see langword="null" />.</value>
		[DefaultValue("")]
		public string ElementName
		{
			get
			{
				return binding.ElementName;
			}
			set
			{
				binding.ElementName = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether <see cref="Subscription"/> should get values asynchronously.
		/// </summary>
		/// <remarks>
		/// Do not confuse <see cref="IsAsync"/> with the asynchronous nature of observables.  <see cref="IsAsync"/>
		/// applies only to reading the source property that returns the data source object, which may or may not 
		/// be an observable sequence.  If reading the source property takes a long time to execute, then it will 
		/// block the UI thread for a long time.  In this case, setting <see cref="IsAsync"/> to true ensures that 
		/// the UI thread is not blocked while the source property is being read.  If the property just-so-happens 
		/// to return an object that is an observable sequence, then the subscription to that observable will be 
		/// unaffected by the value of <see cref="IsAsync"/>.
		/// </remarks>
		/// <value>The default is <see langword="false" />.</value>
		[DefaultValue(false)]
		public bool IsAsync
		{
			get
			{
				return binding.IsAsync;
			}
			set
			{
				binding.IsAsync = value;
			}
		}

		/// <summary>
		/// Gets or sets the path to the binding source property.
		/// </summary>
		/// <value>The path to the binding source.  The default is <see langword="null"/>.</value>
		public PropertyPath Path
		{
			get
			{
				return binding.Path;
			}
			set
			{
				binding.Path = value;
			}
		}

		/// <summary>
		/// Gets or sets the binding source by specifying its location relative to the position of the binding target.
		/// </summary>
		/// <value>A <see cref="RelativeSource"/> object specifying the relative location of the binding source to use.
		/// The default is <see langword="null"/>.</value>
		[DefaultValue("")]
		public RelativeSource RelativeSource
		{
			get
			{
				return binding.RelativeSource;
			}
			set
			{
				binding.RelativeSource = value;
			}
		}

		/// <summary>
		/// Gets or sets the object to use as the binding source.
		/// </summary>
		/// <value>The object to use as the binding source.</value>
		public object Source
		{
			get
			{
				return binding.Source;
			}
			set
			{
				binding.Source = value;
			}
		}

		/// <summary>
		/// Gets or sets an XPath query that returns the value on the XML binding source to use.
		/// </summary>
		/// <value>The XPath query.  The default is <see langword="null"/>.</value>
		[DefaultValue("")]
		public string XPath
		{
			get
			{
				return binding.XPath;
			}
			set
			{
				binding.XPath = value;
			}
		}

		/// <summary>
		/// Gets or sets the name of the <see cref="BindingGroup"/> to which this binding belongs.
		/// </summary>
		/// <value>The name of the <see cref="BindingGroup"/> to which this binding belongs.</value>
		[DefaultValue("")]
		public string BindingGroupName
		{
			get
			{
				return binding.BindingGroupName;
			}
			set
			{
				binding.BindingGroupName = value;
			}
		}

		/// <summary>
		/// Gets or sets the value to use when the binding is unable to return a value.
		/// </summary>
		/// <value>The default value is <see cref="DependencyProperty.UnsetValue"/>.</value>
		public object FallbackValue
		{
			get
			{
				return multiBinding.FallbackValue;
			}
			set
			{
				multiBinding.FallbackValue = value;
			}
		}

		/// <summary>
		/// Gets or sets a string that specifies how to format the binding if it displays the bound value as a string.
		/// </summary>
		/// <value>A string that specifies how to format the binding if it displays the bound value as a string.</value>
		[DefaultValue("")]
		public string StringFormat
		{
			get
			{
				return multiBinding.StringFormat;
			}
			set
			{
				multiBinding.StringFormat = value;
			}
		}

		/// <summary>
		/// Gets or sets the value that is used in the target when the value of the source is <see langword="null"/>.
		/// </summary>
		/// <value>The value that is used in the target when the value of the source is <see langword="null"/>.</value>
		public object TargetNullValue
		{
			get
			{
				return multiBinding.TargetNullValue;
			}
			set
			{
				multiBinding.TargetNullValue = value;
			}
		}

		/// <summary>
		/// Gets or sets the converter to use to convert the source values to or from the target value.
		/// </summary>
		/// <value>A value of type <see cref="IValueConverter"/> that indicates the converter to use.
		/// The default value is <see langword="null" />.</value>
		[DefaultValue("")]
		public IValueConverter Converter
		{
			get
			{
				return converter.ValueConverter;
			}
			set
			{
				converter.ValueConverter = value;
			}
		}

		/// <summary>
		/// Gets or sets the culture in which to evaluate the converter.
		/// </summary>
		/// <value>The default is <see langword="null" />.</value>
		[DefaultValue("")]
		[TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))]
		public CultureInfo ConverterCulture
		{
			get
			{
				return converter.ValueConverterCulture;
			}
			set
			{
				converter.ValueConverterCulture = value;
			}
		}

		/// <summary>
		/// Gets or sets an optional parameter to pass to the <see cref="Converter"/> as additional information.
		/// </summary>
		/// <value>The parameter to pass to the <see cref="Converter"/>.  The default is <see langword="null"/>.</value>
		[DefaultValue("")]
		public object ConverterParameter
		{
			get
			{
				return converter.ValueConverterParameter;
			}
			set
			{
				converter.ValueConverterParameter = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether to raise the <see cref="FrameworkElement.SourceUpdated"/>
		/// event when a value is transferred from the binding target to the binding source.
		/// </summary>
		/// <value><see langword="True"/> if the <see cref="FrameworkElement.SourceUpdated"/> event will be raised
		/// when the binding source value is updated; otherwise, <see langword="false"/>.
		/// The default value is <see langword="false"/>.</value>
		[DefaultValue(false)]
		public bool NotifyOnSourceUpdated
		{
			get
			{
				return multiBinding.NotifyOnSourceUpdated;
			}
			set
			{
				multiBinding.NotifyOnSourceUpdated = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether to raise the <see cref="FrameworkElement.TargetUpdated"/>
		/// event when a value is transferred from the binding source to the binding target.
		/// </summary>
		/// <value><see langword="True"/> if the <see cref="FrameworkElement.TargetUpdated"/> event will be raised
		/// when the binding target value is updated; otherwise, <see langword="false"/>.
		/// The default value is <see langword="false"/>.</value>
		[DefaultValue(false)]
		public bool NotifyOnTargetUpdated
		{
			get
			{
				return multiBinding.NotifyOnTargetUpdated;
			}
			set
			{
				multiBinding.NotifyOnTargetUpdated = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether to raise the <see cref="System.Windows.Controls.Validation.ErrorEvent"/>
		/// attached event on the bound element.
		/// </summary>
		/// <value><see langword="True"/> if the <see cref="System.Windows.Controls.Validation.ErrorEvent"/> attached event will
		/// be raised on the bound element when there is a validation error during source updates; otherwise, 
		/// <see langword="false"/>.  The default value is <see langword="false"/>.</value>
		[DefaultValue(false)]
		public bool NotifyOnValidationError
		{
			get
			{
				return multiBinding.NotifyOnValidationError;
			}
			set
			{
				multiBinding.NotifyOnValidationError = value;
			}
		}

		/// <summary>
		/// Gets or sets a handler you can use to provide custom logic for handling exceptions
		/// that the binding engine encounters during the update of the binding source
		/// value.  This is only applicable if you have associated the <see cref="System.Windows.Controls.ExceptionValidationRule"/>
		/// with your <see cref="Subscription"/> object.
		/// </summary>
		/// <value>A method that provides custom logic for handling exceptions that the binding
		/// engine encounters during the update of the binding source value.</value>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public UpdateSourceExceptionFilterCallback UpdateSourceExceptionFilter
		{
			get
			{
				return multiBinding.UpdateSourceExceptionFilter;
			}
			set
			{
				multiBinding.UpdateSourceExceptionFilter = value;
			}
		}

		/// <summary>
		/// Gets or sets a value that determines the timing of binding source updates.
		/// </summary>
		/// <value>One of the <see cref="UpdateSourceTrigger"/> values.  The default value
		/// is <see cref="System.Windows.Data.UpdateSourceTrigger.Default"/>, which returns the default 
		/// <see cref="System.Windows.Data.UpdateSourceTrigger"/> value of the target dependency property.
		/// However, the default value for most dependency properties is <see cref="System.Windows.Data.UpdateSourceTrigger.PropertyChanged"/>,
		/// while the <see cref="System.Windows.Controls.TextBox.Text"/> property has a default value 
		/// of <see cref="System.Windows.Data.UpdateSourceTrigger.LostFocus"/>.  A programmatic way to determine the default 
		/// <see cref="Binding.UpdateSourceTrigger"/> value of a dependency property is to get the property 
		/// metadata of the property using <see cref="DependencyProperty.GetMetadata(Type)"/> and then
		/// check the value of the <see cref="FrameworkPropertyMetadata.DefaultUpdateSourceTrigger"/> property.</value>
		public UpdateSourceTrigger UpdateSourceTrigger
		{
			get
			{
				return multiBinding.UpdateSourceTrigger;
			}
			set
			{
				multiBinding.UpdateSourceTrigger = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether to include the <see cref="System.Windows.Controls.DataErrorValidationRule"/>.
		/// </summary>
		/// <value><see langword="True"/> to include the <see cref="System.Windows.Controls.DataErrorValidationRule"/>; 
		/// otherwise, <see langword="false"/>.</value>
		[DefaultValue(false)]
		public bool ValidatesOnDataErrors
		{
			get
			{
				return multiBinding.ValidatesOnDataErrors;
			}
			set
			{
				multiBinding.ValidatesOnDataErrors = value;
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether to include the <see cref="System.Windows.Controls.ExceptionValidationRule"/>.
		/// </summary>
		/// <value><see langword="True"/> to include the <see cref="System.Windows.Controls.ExceptionValidationRule"/>; 
		/// otherwise, <see langword="false"/>.</value>
		[DefaultValue(false)]
		public bool ValidatesOnExceptions
		{
			get
			{
				return multiBinding.ValidatesOnExceptions;
			}
			set
			{
				multiBinding.ValidatesOnExceptions = value;
			}
		}

		/// <summary>
		/// Gets a collection of rules that check the validity of the user input.
		/// </summary>
		/// <value>A collection of <see cref="System.Windows.Controls.ValidationRule"/> objects.</value>
		public Collection<ValidationRule> ValidationRules
		{
			get
			{
				return multiBinding.ValidationRules;
			}
		}
		#endregion

		#region Private / Protected
		private readonly ObservableValueConverter converter = new ObservableValueConverter();
		private readonly Binding binding, proxyBinding;
		private readonly MultiBinding multiBinding;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="Subscription" /> class.
		/// </summary>
		[ContractVerification(false)]
		public Subscription()
		{
			binding = new Binding()
			{
				Mode = BindingMode.OneWay
			};

			proxyBinding = new Binding("Value")
			{
				Mode = BindingMode.TwoWay,
				Source = converter
			};

			multiBinding = new MultiBinding()
			{
				Converter = converter,
				Bindings =
				{
					binding, 
					proxyBinding
				}
			};
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="Subscription" /> class with an initial path.
		/// </summary>
		/// <param name="path">The initial <see cref="Path"/> for the binding.</param>
		public Subscription(string path)
			: this()
		{
			binding.Path = new PropertyPath(path);
		}
		#endregion

		#region Methods
		[ContractInvariantMethod]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
		private void ObjectInvariant()
		{
			Contract.Invariant(converter != null);
			Contract.Invariant(binding != null);
			Contract.Invariant(proxyBinding != null);
			Contract.Invariant(multiBinding != null);
		}

		/// <summary>
		/// Returns an object that should be set on the property where this binding and extension are applied.
		/// </summary>
		/// <param name="serviceProvider">The object that can provide services for the markup extension.  May be <see langword="null" />.</param>
		/// <returns>The value to set on the binding target property.</returns>
		public override object ProvideValue(IServiceProvider serviceProvider)
		{
			if (serviceProvider != null)
			{
				var service = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

				if (service != null)
				{
					var target = service.TargetObject as DependencyObject;

					if (target != null && DesignerProperties.GetIsInDesignMode(target))
					{
						var property = service.TargetProperty as DependencyProperty;

						return property != null && property.PropertyType == typeof(System.Collections.IEnumerable)
							? Binding.DoNothing
							: null;
					}
				}
			}

			var expression = (MultiBindingExpression) multiBinding.ProvideValue(serviceProvider);

			converter.Expression = expression;

			return expression;
		}

		/// <summary>
		/// Disposes of the active subscription, if any, to an observable data source.
		/// </summary>
		public void Dispose()
		{
			converter.Dispose();
		}
		#endregion
	}
}